//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

//
//	File Name:	animExportUtil.h
//
//	Description: an animation export utility which illustrates how to
//	use the MAnimUtil animation helper class, as well as how to export
//	animation using the Maya .anim format
//
//

// *****************************************************************************

// INCLUDED HEADER FILES

#include <maya/MFStream.h>
#include <stdlib.h>
#include <string.h>

#include <maya/MGlobal.h>
#include <maya/MString.h>
#include <maya/MFnPlugin.h>
#include <maya/MPxFileTranslator.h>
#include <maya/MDagPath.h>
#include <maya/MPlug.h>
#include <maya/MPlugArray.h>
#include <maya/MObjectArray.h>
#include <maya/MItDag.h>
#include <maya/MItDependencyNodes.h>
#include <maya/MFnAttribute.h>
#include <maya/MFnSet.h>
#include <maya/MSelectionList.h>
#include <maya/MItSelectionList.h>

#include <maya/MFnAnimCurve.h>
#include <maya/MAnimUtil.h>

#include "animExportUtil.h"
#include "animFileExport.h"

#if defined (OSMac_)
using namespace std;
extern "C" int strcasecmp (const char *, const char *);
#endif
// *****************************************************************************

// HELPER METHODS

// *****************************************************************************

#ifndef min
static inline double
min (double a, double b)
{
	return (a < b ? a : b);
}
#endif

#ifndef max
static inline double
max (double a, double b)
{
	return (a > b ? a : b);
}
#endif

// *****************************************************************************

// PUBLIC MEMBER FUNCTIONS

TanimExportUtil::TanimExportUtil()
{
}

TanimExportUtil::~TanimExportUtil()
{
}

MStatus
TanimExportUtil::writer (
	const MFileObject &file,
	const MString &/*options*/,
	FileAccessMode mode
)
{
	// Create the export file
	//
	ofstream animFile (file.fullName().asChar());

	// Create a selection list to hold the objects we want to export
	//
	MSelectionList list;
	if (mode == kExportActiveAccessMode) {
		// Use the active list
		//
		MGlobal::getActiveSelectionList (list);
	}
	else {
		// Create a selection list with all the objects in the world
		//
		// Add the top level dag objects
		//
		MItDag dagIt (MItDag::kBreadthFirst);
		for (dagIt.next(); !dagIt.isDone(); dagIt.next()) {
			MDagPath path;
			if (dagIt.getPath (path) != MS::kSuccess) {
				continue;
			}
			list.add (path);
			// We only want the top level objects (since we will walk
			// down the hierarchy later
			//
			dagIt.prune ();
		}
		// Gather the rest of the dependency nodes
		//
		MItDependencyNodes nodeIt;
		for (; !nodeIt.isDone(); nodeIt.next()) {
			MObject node = nodeIt.item();
			if (node.isNull()) {
				continue;
			}
			// We have already saved the dag objects, so skip them here
			//
			if (node.hasFn (MFn::kDagNode)) {
				continue;
			}
			// Watch out for characters, and only write the top level
			// character
			//
			if (node.hasFn (MFn::kCharacter)) {
				// Find out which (if any) sets this node belongs to
				//
				// Get the message attribute
				//
				MFnDependencyNode fnNode (node);
				MObject aMessage = fnNode.attribute (MString ("message"));
				MPlug messagePlug (node, aMessage);
				// Now find what it is connected to as a source plug
				//
				MPlugArray srcPlugArray;
				messagePlug.connectedTo (srcPlugArray, false, true);
				// Now walk through each connection and see if any is to
				// another character
				//
				unsigned int numPlugs = srcPlugArray.length();
				bool belongsToCharacter = false;
				for (unsigned int i = 0; (i < numPlugs) && !belongsToCharacter; i++) {
					const MPlug &plug = srcPlugArray[i];
					if (!plug.node().hasFn (MFn::kCharacter)) {
						continue;
					}
					belongsToCharacter = true;
				}
				if (!belongsToCharacter) {
					list.add (node);
				}
				continue;
			}
			// A superfluous test to filter out unanimated objects, just to
			// show how to use MAnimUtil
			//
			if (!MAnimUtil::isAnimated (node)) {
				continue;
			}
			list.add (node);
		}
	}

	// The Maya .anim format needs to know the bounds of the animation
	// since it could be used in pasteKey operations (which support
	// scaling in time), so gather that information now
	//
	// Find all the plugs that are animated in our selection list
	//
	MPlugArray animatedPlugs;
	MAnimUtil::findAnimatedPlugs (list, animatedPlugs);
	unsigned int numPlugs = animatedPlugs.length();
	bool hasTime = false;
	double startTime = 0.0;
	double endTime = 0.0;
	bool hasUnitless = false;
	double startUnitless = 0.0;
	double endUnitless = 0.0;
	unsigned int i;
	// For each animated plug, determine the bounds of the animation
	//
	for (i = 0; i < numPlugs; i++) {
		MPlug plug = animatedPlugs[i];
		MObjectArray animation;
		// Find the animation curve(s) that animate this plug
		//
		if (!MAnimUtil::findAnimation (plug, animation)) {
			continue;
		}
		unsigned int numCurves = animation.length();
		for (unsigned int j = 0; j < numCurves; j++) {
			MObject animCurveNode = animation[j];
			if (!animCurveNode.hasFn (MFn::kAnimCurve)) {
				continue;
			}
			MFnAnimCurve animCurve (animCurveNode);
			unsigned int numKeys = animCurve.numKeys();
			if (numKeys == 0) {
				continue;
			}
			if (animCurve.isUnitlessInput()) {
				if (!hasUnitless) {
					startUnitless = animCurve.unitlessInput (0);
					endUnitless = animCurve.unitlessInput (numKeys - 1);
					hasUnitless = true;
				}
				else {
					startUnitless = min (startUnitless, animCurve.unitlessInput (0));
					endUnitless = max (endUnitless, animCurve.unitlessInput (numKeys - 1));
				}
			}
			else {
				if (!hasTime) {
					startTime = animCurve.time (0).value();
					endTime = animCurve.time (numKeys - 1).value();
					hasTime = true;
				}
				else {
					startTime = min (startTime, animCurve.time (0).value());
					endTime = max (endTime, animCurve.time (numKeys - 1).value());
				}
			}
		}
	}

	// Write out the header information
	//
	animWriter writer;
	writer.writeHeader (animFile, startTime, endTime, startUnitless, endUnitless);

	// Now write out the animation for each object on the selection list
	//
	unsigned int numObjects = list.length();
	for (i = 0; i < numObjects; i++) {
		MDagPath path;
		MObject node;
		if (list.getDagPath (i, path) == MS::kSuccess) {
			write (animFile, path);
		}
		else if (list.getDependNode (i, node) == MS::kSuccess) {
			write (animFile, node);
		}
	}

	animFile.flush();
	animFile.close();

	return (MS::kSuccess);
}

void
TanimExportUtil::write (ofstream &animFile, const MDagPath &path)
{
	// Walk through the dag breadth first
	//
	MItDag dagIt (MItDag::kDepthFirst);
	dagIt.reset (path, MItDag::kDepthFirst);
	for (; !dagIt.isDone(); dagIt.next()) {
		MDagPath thisPath;
		if (dagIt.getPath (thisPath) != MS::kSuccess) {
			continue;
		}
		// Find the animated plugs for this object
		//
		MPlugArray animatedPlugs;
		MObject node = thisPath.node();
		MFnDependencyNode fnNode (node);
		MAnimUtil::findAnimatedPlugs (thisPath, animatedPlugs);
		unsigned int numPlugs = animatedPlugs.length();
		if (numPlugs == 0) {
			// If the object is not animated, then write out place holder
			// information
			//
			animFile << "anim " << fnNode.name().asChar() << " " << dagIt.depth() << " " << thisPath.childCount() << " 0;\n";
		}
		else {
			// Otherwise write out each animation curve
			//
			writeAnimatedPlugs (animFile, animatedPlugs, fnNode.name(), dagIt.depth(), thisPath.childCount());
		}
	}
}

void
TanimExportUtil::write (ofstream &animFile, const MObject &node)
{
	// Watch out for characters and handle them a little differently
	//
	if (node.hasFn (MFn::kCharacter)) {
		MObjectArray characterList;
		characterList.append (node);
		MIntArray depths;
		depths.append (0);
		unsigned int current = 0;
		while (current < characterList.length()) {
			const MObject &thisNode = characterList[current];
			int thisDepth = depths[current++];
			// If this node is a character, then check for any immediate
			// subCharacters
			//
			MFnSet fnSet (thisNode);
			// Now find the set members
			//
			MSelectionList members;
			fnSet.getMembers (members, false);
			unsigned int childCount = 0;
			MItSelectionList iter (members, MFn::kCharacter);
			for (; !iter.isDone(); iter.next()) {
				MObject childNode;
				iter.getDependNode (childNode);
				characterList.insert (childNode, current + childCount);
				depths.insert (thisDepth + 1, current + childCount);
				childCount++;
			}
			// Find the animated plugs for this object
			//
			MPlugArray animatedPlugs;
			MAnimUtil::findAnimatedPlugs (thisNode, animatedPlugs);
			unsigned int numPlugs = animatedPlugs.length();
			if (numPlugs == 0) {
				// If the object is not animated, then write out place holder
				// information
				//
				animFile << "anim " << fnSet.name().asChar() << " " << thisDepth << " " << childCount << " 0;\n";
			}
			else {
				// Otherwise write out each animation curve
				//
				writeAnimatedPlugs (animFile, animatedPlugs, fnSet.name(), thisDepth, childCount);
			}
		}
		return;
	}
	// Find the animated plugs for this object
	//
	MPlugArray animatedPlugs;
	MFnDependencyNode fnNode (node);
	MAnimUtil::findAnimatedPlugs (node, animatedPlugs);
	unsigned int numPlugs = animatedPlugs.length();
	if (numPlugs != 0) {
		// If the object is animated the write out each animation curve
		//
		writeAnimatedPlugs (animFile, animatedPlugs, fnNode.name(), 0, 0);
	}
}

void
TanimExportUtil::writeAnimatedPlugs (
	ofstream &animFile,
	const MPlugArray &animatedPlugs,
	const MString &nodeName,
	unsigned int depth,
	unsigned int childCount
)
{
	// Walk through each animated plug and write out the animation curve(s)
	//
	unsigned int numPlugs = animatedPlugs.length();
	for (unsigned int i = 0; i < numPlugs; i++) {
		MPlug plug = animatedPlugs[i];
		MObjectArray animation;
		if (!MAnimUtil::findAnimation (plug, animation)) {
			continue;
		}
		// Write out the plugs' anim statement
		//
		animFile << "anim ";
		// build up the full attribute name
		//
		MPlug attrPlug (plug);
		MObject attrObj = attrPlug.attribute();
		MFnAttribute fnAttr (attrObj);
		MString fullAttrName (fnAttr.name());
		attrPlug = attrPlug.parent();
		while (!attrPlug.isNull()) {
			attrObj = attrPlug.attribute();
			MFnAttribute fnAttr2 (attrObj);
			fullAttrName = fnAttr2.name() + "." + fullAttrName;
			attrPlug = attrPlug.parent();
		}
		attrObj = plug.attribute();
		MFnAttribute fnLeafAttr (attrObj);
		animFile << fullAttrName.asChar() << " " << fnLeafAttr.name().asChar() << " " << nodeName.asChar() << " " << depth << " " << childCount << " " << i << ";\n";
		unsigned int numCurves = animation.length();
		// Write out each animation curve that animates the plug
		//
		for (unsigned int j = 0; j < numCurves; j++) {
			MObject animCurveNode = animation[j];
			if (!animCurveNode.hasFn (MFn::kAnimCurve)) {
				continue;
			}
			animWriter writer;
			writer.writeAnimCurve (animFile, &animCurveNode);
		}
	}
}

bool
TanimExportUtil::haveWriteMethod () const
{
	return (true);
}

MString
TanimExportUtil::defaultExtension () const
{
	return (MString("anim"));
}

MPxFileTranslator::MFileKind
TanimExportUtil::identifyFile (
	const MFileObject &file,
	const char * /*buffer*/,
	short /*size*/
) const
{
	const char *name = file.name().asChar();
	int   nameLength = (int)strlen(name);

	if ((nameLength > 5) && !strcasecmp(name+nameLength-5, ".anim")) {
		return (kIsMyFileType);
	}

	return (kNotMyFileType);
}

/* static */ void *
TanimExportUtil::creator ()
{
	return (new TanimExportUtil);
}

//--------------------------------------------------------------------------------
//	Plugin management
//--------------------------------------------------------------------------------

MStatus
initializePlugin (MObject obj)
{
	MStatus status;
	MFnPlugin plugin (obj, PLUGIN_COMPANY, "3.0");
	status = plugin.registerFileTranslator ("animExportUtil", "", TanimExportUtil::creator);
	return (status);
}

MStatus
uninitializePlugin (MObject obj)
{
	MFnPlugin plugin (obj);
	plugin.deregisterFileTranslator ("animExportUtil");
	return (MS::kSuccess);
}
